package my.mark.mybaibaoxiang.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.zip.*;

/**
 * 文件解压缩ZIP
 * 
 */
public class ZipUtil {

    private static final Logger logger = LoggerFactory.getLogger(ZipUtil.class);

    /**
     * 文件压缩.
     * 
     * @param srcRootDir
     *            压缩文件夹根目录的子路径
     * @param file
     *            当前递归压缩的文件或目录对象
     * @param zos
     *            压缩文件存储对象流
     * @throws Exception
     */
    private static void zip(String srcRootDir, File file, ZipOutputStream zos) throws Exception {
        if (file == null) {
            return;
        }
        // 如果是文件，则直接压缩该文件

        if (file.isFile()) {
            int count, bufferLen = 1024;
            byte data[] = new byte[bufferLen];
            // 获取文件相对于压缩文件夹根目录的子路径
            String subPath = file.getAbsolutePath();
            int index = subPath.indexOf(srcRootDir);
            if (index != -1) {
                subPath = subPath.substring(srcRootDir.length() + File.separator.length());
            }
            ZipEntry entry = new ZipEntry(subPath);
            zos.putNextEntry(entry);
            BufferedInputStream bis = null;
            try {
                bis = new BufferedInputStream(new FileInputStream(file));
                while ((count = bis.read(data, 0, bufferLen)) != -1) {
                    zos.write(data, 0, count);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                try {
                    if (bis != null) {
                        bis.close();
                    }

                    zos.closeEntry();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
        // 如果是目录，则压缩整个目录
        else {
            // 压缩目录中的文件或子目录
            File[] childFileList = file.listFiles();
            for (int n = 0; n < childFileList.length; n++) {
                // modify by dusong indexOf not be use
                // childFileList[n].getAbsolutePath().indexOf(file.getAbsolutePath());
                zip(srcRootDir, childFileList[n], zos);
            }
        }
    }

    /**
     * 对文件或文件目录进行压缩.
     * 
     * @param srcPath
     *            要压缩的源文件路径。如果压缩一个文件，则为该文件的全路径；如果压缩一个目录，则为该目录的顶层目录路径
     * @param zipPath
     *            压缩文件保存的路径。注意：zipPath不能是srcPath路径下的子文件夹
     * @param zipFileName
     *            压缩文件名
     * @return
     * @throws Exception
     */
    public static void zip(String srcPath, String zipPath, String zipFileName) throws Exception {
        if (StringUtils.isEmpty(srcPath) || StringUtils.isEmpty(zipPath) || StringUtils.isEmpty(zipFileName)) {
            throw new Exception("PARAMETER IS NULL");
        }
        
        File srcFile = new File(srcPath);
        // 判断压缩文件保存的路径是否为源文件路径的子文件夹，如果是，则抛出异常（防止无限递归压缩的发生）
        if (srcFile.isDirectory() && zipPath.indexOf(srcPath) != -1) {
            throw new Exception("INVALID_PARAMETER,zipPath must not be the child directory of srcPath.");
        }
        // 判断压缩文件保存的路径是否存在，如果不存在，则创建目录
        File zipDir = new File(zipPath);
        if (!zipDir.exists() || !zipDir.isDirectory()) {
            zipDir.mkdirs();
        }
        // 创建压缩文件保存的文件对象
        String zipFilePath = zipPath + File.separator + zipFileName;
        File zipFile = new File(zipFilePath);
        if (zipFile.exists()) {
            // 检测文件是否允许删除，如果不允许删除，将会抛出SecurityException
            SecurityManager securityManager = new SecurityManager();
            securityManager.checkDelete(zipFilePath);
            // 删除已存在的目标文件
            try {
                Files.delete(zipFile.toPath());
            } catch (IOException e) {
                logger.warn("文件删除失败");
                e.printStackTrace();
            }
        }
        try (CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(zipFile), new CRC32());
             ZipOutputStream zos = new ZipOutputStream(cos)) {
            
            // 如果只是压缩一个文件，则需要截取该文件的父目录
            String srcRootDir = srcPath;
            if (srcFile.isFile()) {
                int index = srcPath.lastIndexOf(File.separator);
                if (index != -1) {
                    srcRootDir = srcPath.substring(0, index);
                }
            }
            // 调用递归压缩方法进行目录或文件压缩
            zip(srcRootDir, srcFile, zos);
            zos.flush();
        } catch (Exception e) {
            throw new Exception("文件压缩失败", e);
        }
    }

    /**
     * 解压缩zip包.
     * 
     * @param zipFilePath zip文件的全路径
     * @param unzipFilePath 解压后的文件保存的路径
     * @param includeZipFileName 解压后的文件保存的路径是否包含压缩文件的文件名。true-包含；false-不包含
     * @throws Exception
     */
    @SuppressWarnings({ "unchecked", "resource" })
    public static void unzip(String zipFilePath, String unzipFilePath, boolean includeZipFileName) throws Exception {
        if (StringUtils.isEmpty(zipFilePath) || StringUtils.isEmpty(unzipFilePath)) {
            throw new Exception("PARAMETER IS NULL");
        }
        File zipFile = new File(zipFilePath);
        // 如果解压后的文件保存路径包含压缩文件的文件名，则追加该文件名到解压路径
        if (includeZipFileName) {
            String fileName = zipFile.getName();
            if (StringUtils.isNotEmpty(fileName)) {
                fileName = fileName.substring(0, fileName.lastIndexOf("."));
            }
            unzipFilePath = unzipFilePath + File.separator + fileName;
        }
        // 创建解压缩文件保存的路径
        File unzipFileDir = new File(unzipFilePath);
        if (!unzipFileDir.exists() || !unzipFileDir.isDirectory()) {
            unzipFileDir.mkdirs();
        }
        // 开始解压
        ZipEntry entry = null;
        String entryFilePath = null;
        String entryDirPath = null;
        File entryFile = null;
        File entryDir = null;
        int index = 0;
        int count = 0;
        int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];
        
        try (ZipFile zip = new ZipFile(zipFile)) {
            
            Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zip.entries();
            // 循环对压缩包里的每一个文件进行解压
            while (entries.hasMoreElements()) {
                entry = entries.nextElement();
                // 构建压缩包中一个文件解压后保存的文件全路径
                entryFilePath = unzipFilePath + File.separator + entry.getName();
                // 构建解压后保存的文件夹路径
                index = entryFilePath.lastIndexOf(File.separator);
                if (index != -1) {
                    entryDirPath = entryFilePath.substring(0, index);
                } else {
                    entryDirPath = "";
                }
                entryDir = new File(entryDirPath);
                // 如果文件夹路径不存在，则创建文件夹
                if (!entryDir.exists() || !entryDir.isDirectory()) {
                    entryDir.mkdirs();
                }
                // 创建解压文件
                entryFile = new File(entryFilePath);
                if (entryFile.exists()) {
                    // 检测文件是否允许删除，如果不允许删除，将会抛出SecurityException
                    SecurityManager securityManager = new SecurityManager();
                    securityManager.checkDelete(entryFilePath);
                    // 删除已存在的目标文件
                    Files.delete(entryFile.toPath());
                }
                
                try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(entryFile));
                        BufferedInputStream bis = new BufferedInputStream(zip.getInputStream(entry)))
                {
                    // 写入文件
                    while ((count = bis.read(buffer, 0, bufferSize)) != -1) {
                        bos.write(buffer, 0, count);
                    }
                    
                    bos.flush();
                } catch (Exception e) {
                    throw new Exception("文件解压缩失败", e);
                }
            }
        } catch (Exception e) {
            throw new Exception("文件解压缩失败", e);
        }
    }
    
    /**
     * 压缩文件
     * @param needZipPath
     * @param targetPath
     * @param fileName
     * @throws Exception
     */
    public static void compressZip(String needZipPath, String targetPath, String fileName) throws Exception {
        String targetFile = StringUtil.builder(100, targetPath, File.separator, fileName);
        Files.deleteIfExists(Paths.get(targetFile));
        ZipOutputStream zipOutput = null;
        try  {
            zipOutput = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(targetFile)));
            File srcFile = new File(needZipPath);
            compressZip(zipOutput, srcFile, srcFile.getName());
            zipOutput.flush();
        } catch (Exception e) {
            throw e;
        } finally {
            if (zipOutput != null) {
                zipOutput.closeEntry();
                zipOutput.close();
            }
        }
        
    }
    
    /**
     * 因为子文件夹中可能还有文件夹，所以进行递归
     *
     */
     private static void compressZip(ZipOutputStream zipOutput, File file, String base) throws Exception {

         if(file.isDirectory()){
             File[] listFiles = file.listFiles();// 列出所有的文件

             for(File fi : listFiles){
                 if(fi.isDirectory()){
                     compressZip(zipOutput, fi, base + "/" + fi.getName());
                 }else{
                     zip(zipOutput, fi, base);
                 }
             }
         }else{
             zip(zipOutput, file, base);
         }
     }

     /**
     * 压缩的具体操作
     *
     */
     public static void zip(ZipOutputStream zipOutput, File file, String base) throws Exception {
         ZipEntry zEntry = new ZipEntry(base + File.separator + file.getName());
         zipOutput.putNextEntry(zEntry);

         try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {

             byte[] buffer = new byte[1024];
             int read = 0;
             while ((read =bis.read(buffer)) != -1){
                 zipOutput.write(buffer, 0, read);
             }
         } catch (Exception e) {
             throw e;
         }
     }
}